﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MapViewer.Loaders
{
	class CombineSourceBuilder
	{
		private const string fileCenterX = "xCenter";
		private const string fileCenterZ = "zCenter";
		private const string fileWidth = "width";
		private const string fileHeight = "height";
		private const string fileColors = "colors";
		private const string fileZoom = "scale";
		private int minZoom = int.MaxValue;

		internal IList<NamedBinaryTag.TagFile> LoadFiles(IEnumerator<System.IO.Stream> enumerator)
		{
			IList<NamedBinaryTag.TagFile> files = new List<NamedBinaryTag.TagFile>();
			while (enumerator.MoveNext())
			{
				NamedBinaryTag.TagFile dataFile = new NamedBinaryTag.TagFile();
				if (enumerator.Current == null)
					continue;
				using (System.IO.Stream stream = enumerator.Current)
				{
					dataFile.Load(stream);
				}
				files.Add(dataFile);
			}
			return files;
		}

		internal System.Windows.Rect ComputeBounds(IList<NamedBinaryTag.TagFile> dataFiles)
		{
			System.Windows.Rect bounds = new System.Windows.Rect();
			bool first = true;
			foreach (var dataFile in dataFiles)
			{
				NamedBinaryTag.TagCompound imageRoot = DataTag(dataFile);
				int scale = 1 << Byte(imageRoot, fileZoom);
				minZoom = Math.Min(minZoom, scale);
				System.Windows.Point center = new System.Windows.Point(Int(imageRoot, fileCenterX), Int(imageRoot, fileCenterZ));
				System.Windows.Size sizeImage = new System.Windows.Size(Short(imageRoot, fileWidth), Short(imageRoot, fileHeight));
				System.Windows.Size sizeTerran = new System.Windows.Size(sizeImage.Width * scale, sizeImage.Height * scale);
				System.Windows.Point topLeft = new System.Windows.Point(center.X - sizeTerran.Width / 2, center.Y - sizeTerran.Height / 2);
				if (first)
					bounds = new System.Windows.Rect(topLeft, sizeTerran);
				else
					bounds.Union(new System.Windows.Rect(topLeft, sizeTerran));
				first = false;
			}
			return bounds;
		}

		internal byte[] CreateImage(NamedBinaryTag.TagFile[] dataFiles, System.Windows.Rect bounds)
		{
			System.Windows.Size finalSize = ComputeImageSize(bounds);
			byte[] data = new byte[(int)finalSize.Width * (int)finalSize.Height];
			for (int i = 0; i < dataFiles.Length; i++)
			{
				NamedBinaryTag.TagCompound imageRoot = DataTag(dataFiles[i]);
				int scale = 1 << Byte(imageRoot, fileZoom);
				int relScale = scale / minZoom;
				System.Windows.Point center = new System.Windows.Point(Int(imageRoot, fileCenterX), Int(imageRoot, fileCenterZ));
				System.Windows.Size sizeImage = new System.Windows.Size(Short(imageRoot, fileWidth), Short(imageRoot, fileHeight));
				System.Windows.Size sizeTerran = new System.Windows.Size(sizeImage.Width * scale, sizeImage.Height * scale);
				System.Windows.Point topLeft = new System.Windows.Point(center.X - sizeTerran.Width / 2, center.Y - sizeTerran.Height / 2);
				int startX = (int)Math.Round((topLeft.X - bounds.X) / (float)minZoom);
				int startY = (int)Math.Round((topLeft.Y - bounds.Y) / (float)minZoom);
				byte[] imageData = Bytes(imageRoot, fileColors);
				for (int y = 0; y < sizeImage.Height; y++)
				{
					int startOffset = (y * relScale + startY) * (int)finalSize.Width;
					int imageOffset = y * (int)sizeImage.Width;
					for (int x = 0; x < sizeImage.Width; x++)
					{
						if (imageData[imageOffset + x] != 0)
							for (int sX = 0; sX < relScale; sX++)
								for (int sY = 0; sY < relScale; sY++)
								{
									data[startOffset + (sY * (int)finalSize.Width) + startX + x * relScale + sX] = imageData[imageOffset + x];
								}
					}
				}
			}
			return data;
		}

		internal NamedBinaryTag.ITag MakeDataTag(IList<NamedBinaryTag.TagFile> dataFiles, byte[] image, System.Windows.Rect bounds)
		{
			NamedBinaryTag.TagCompound dataTag = new NamedBinaryTag.TagCompound() { Name = "data" };
			System.Windows.Size size = ComputeImageSize(bounds);
			System.Windows.Point center = new System.Windows.Point(bounds.X + bounds.Width / 2, bounds.Y + bounds.Height / 2);
			int minZoom = this.minZoom;
			byte zoom = 0;
			while (minZoom > 1)
			{
				zoom++;
				minZoom >>= 1;
			}
			dataTag.Items.Add(new NamedBinaryTag.TagShort() { Name = fileWidth, Value = (short)size.Width });
			dataTag.Items.Add(new NamedBinaryTag.TagShort() { Name = fileHeight, Value = (short)size.Height });
			dataTag.Items.Add(new NamedBinaryTag.TagInt() { Name = fileCenterX, Value = (int)center.X });
			dataTag.Items.Add(new NamedBinaryTag.TagInt() { Name = fileCenterZ, Value = (int)center.Y });
			dataTag.Items.Add(new NamedBinaryTag.TagByte() { Name = fileZoom, Value = zoom });
			dataTag.Items.Add(new NamedBinaryTag.TagByte() { Name = "dimension", Value = Byte(DataTag(dataFiles[0]), "dimension") });
			dataTag.Items.Add(new NamedBinaryTag.TagByteArray() { Name = fileColors, Value = image });
			return dataTag;
		}

		public System.Windows.Size ComputeImageSize(System.Windows.Rect bounds)
		{
			return new System.Windows.Size((int)Math.Ceiling(bounds.Width / (float)minZoom), (int)Math.Ceiling(bounds.Height / (float)minZoom));
		}

		private NamedBinaryTag.TagCompound DataTag(NamedBinaryTag.TagFile file)
		{
			return file.RootTag["data"] as NamedBinaryTag.TagCompound;
		}

		private byte[] Bytes(NamedBinaryTag.TagCompound data, string name)
		{
			return (data[name] as NamedBinaryTag.TagByteArray).Value;
		}

		private short Short(NamedBinaryTag.TagCompound data, string name)
		{
			return (data[name] as NamedBinaryTag.TagShort).Value;
		}

		private int Int(NamedBinaryTag.TagCompound data, string name)
		{
			return (data[name] as NamedBinaryTag.TagInt).Value;
		}

		private byte Byte(NamedBinaryTag.TagCompound data, string name)
		{
			return (data[name] as NamedBinaryTag.TagByte).Value;
		}
	}
}
